<?php
/**
 * Manager/Mapper for DbModel
 */

class DbManager {

    /**
     * @param string $model model class
     * @param string $table table name
     * @param string $key primary key
     */
    public function __construct($model,$table,$key='id') {
        $this->model=$model;
        $this->table=$table;
        $this->key=$key;
        // guess field definitions
        $this->fields=$this->makeFields($model);
        // echo '<pre>'; print_r($this);
    }

    /**
     * create a record from provided data
     * then return model instance
     * @param array $data
     * @return DbModel
     */
    public function create($data) {
        // build a record instance from data
        $class=$this->model;
        $record=new $class($data);
        // is primary key set ?
        $key=$this->key;
        $auto_id=!isset($record->$key);
        // build insertion SQL
        $sql=$this->buildSqlInsert($record); // echo $sql;
        // execute insertion
        $this->query($sql);
        // update automatic primary key if necessary
        if ($auto_id)
            $record->$key=mysql_insert_id();
        return $record;
    }/**/

    /**
     * get all records
     */
    public function all() {
        return $this->modelsFromSql('SELECT * FROM '.$this->table);
    }

    /**
     * get one record by primary key
     * @param string $id primary key value
     */
    public function get($id) {
        // search for primary key
        $key=$this->getPrimaryKey();
        $sql='SELECT * FROM '.$this->table.' WHERE '.$key->getDbName().'='.$id;
        return $this->modelFromSql($sql);
    }


    /**
     * return primary key field
     * @return DbField
     */
    public function getPrimaryKey() {
        return $this->fields[$this->key];
    }

    /**
     * builds insertion SQL from data
     * @param DbModel $record
     */
    public function buildSqlInsert($record) {
        $sql='INSERT INTO '.$this->table.' SET ';
        // lopping through fields
        foreach ($this->fields as $key => $field) {
            // if not set in record, continue
            if (!isset($record->$key))
                continue;
            // field checking
            if (!$this->fields[$key]->isValid($record->$key)) {
                throw new DbFieldInvalid("$key is not valid");
                return false;
            }
            // getting database name
            $sql.=' '.$this->fields[$key]->getDbName()
            // converting value for db
            .'='.$this->fields[$key]->toDb($record->$key)
            .',';
        }
        // wiping last ,
        $sql=substr($sql,0,-1);
        return $sql;
    }


    /**
     * @var array(DbField) model definition
     */
    public $fields=array();

    /**
     * @var string model class
     */
    protected $model;

    /**
     * @var string database table name
     */
    protected $table;

    /**
     * @var string index of primary key field in $fields
     */
    protected $key;

    /**
     * @var string table's primary key field
     */

    /**
     * return objects from a select query
     */
    protected function modelsFromSql($sql) {
        $result=$this->query($sql);
        $records=array();
        while ($record=$this->modelFromResult($result)) {
            $records[]=$record;
        }
        return $records;
    }

    /**
     * return a single object from SQL select
     */
    protected function modelFromSql($sql) {
        $result=$this->query($sql);
        return $this->modelFromResult($result);
    }


    /**
     * return an object from an SQL result
     * @param resource $result sql result
     */
    protected function modelFromResult($result) {
        if (!$line=mysql_fetch_assoc($result))
            return false;
        $class=$this->model;
        $record=new $class;
        foreach ($line as $key => $value) {
            if (isset($this->fields[$key]))
                $record->$key=$this->fields[$key]->toPhp($value);
        }
        return $record;
    }

    /**
     * builds fields from DbModel class
     * tries to guess correct class upon default's type
     * @param string $class
     * @param string $key
     * @return DbField
     */
    protected function makeFields($class) {
        $fields=array();
        // here we build an instance rather than class_vars to avoid class static vars
        $record=new $class;
        foreach (get_object_vars($record) as $key => $value) {
            switch (gettype($value)) {
                case 'integer':
                    $field=new DbIntegerField($key);
                    break;
                case 'string':
                    $field=new DbTextField($key);
                    break;
                default:
                    $field=new DbField($key);
            }
            $fields[$key]=$field;
        }
        return $fields;
    }

    /**
     * execute SQL query
     * @return resource sql result
     */
    protected function query($sql) {
        if (!$result=mysql_query($sql))
            throw new Exception("no result for $sql");
        return $result;
    }
}